package com.swak.vertx.protocol.http;

import java.util.concurrent.CompletionStage;
import java.util.function.BiConsumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;

import com.swak.Constants;
import com.swak.metrics.MetricsFactory;
import com.swak.utils.StringUtils;
import com.swak.validator.Validator;
import com.swak.vertx.cache.GlobalCacheService;
import com.swak.vertx.invoker.MethodInvoker;
import com.swak.vertx.protocol.http.adapter.Adapter;
import com.swak.vertx.protocol.http.adapter.Adapter.Context;
import com.swak.vertx.protocol.http.adapter.InvokeAdapter;
import com.swak.vertx.protocol.http.adapter.MethodCacheAdapter;
import com.swak.vertx.protocol.http.adapter.MetricsAdapter;
import com.swak.vertx.protocol.http.adapter.ParamCacheAdapter;
import com.swak.vertx.protocol.http.adapter.ParamParseAdapter;
import com.swak.vertx.protocol.http.adapter.PermissionAdapter;
import com.swak.vertx.protocol.http.adapter.ResultAdapter;
import com.swak.vertx.protocol.http.chain.Chain;
import com.swak.vertx.security.SecurityHandler;

import io.vertx.core.http.HttpHeaders;
import io.vertx.core.impl.ContextInternal;
import io.vertx.ext.web.RoutingContext;

/**
 * 请求执行器, 定义为 http 服务入口
 *
 * @author: lifeng
 * @date: 2020/3/29 19:48
 */
public class RouterHandlerAdapter implements RouterHandler, InitializingBean {

	protected Logger logger = LoggerFactory.getLogger(RouterHandler.class);

	@Autowired(required = false)
	private Validator validator;
	@Autowired
	private ResultHandler resultHandler;
	@Autowired(required = false)
	private GlobalCacheService cacheStorage;
	@Autowired(required = false)
	private MetricsFactory metricsFactory;
	@Autowired(required = false)
	private SecurityHandler securityHandler;
	private final BiConsumer<Context, Throwable> chainComplete;
	private ParamParser paramParser;

	public RouterHandlerAdapter() {
		this.chainComplete = this::chainComplete;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		this.paramParser = new ParamParser(validator);
	}

	/**
	 * 构建执行链
	 */
	@Override
	public Chain buildChain(MethodInvoker handler) {

		// 这种情况直接不需要使用执行链
		if (securityHandler == null && !(cacheStorage != null && cacheStorage.isValid())) {
			return Chain.NONE;
		}

		// 功能集合
		int ops = 0;
		ops |= (securityHandler != null && handler.useSecurity()) ? Constants.Route_Func_Security : 0; // 0000 0001
		ops |= (metricsFactory != null && handler.useMetrics()) ? Constants.Route_Func_Metrics : 0; // 0000 0010
		ops |= ((cacheStorage != null && cacheStorage.isValid()) && handler.useMethodCache())
				? Constants.Route_Func_Method_Cache
				: 0; // 0000 0100
		ops |= ((cacheStorage != null && cacheStorage.isValid()) && handler.useParamCache())
				? Constants.Route_Func_Param_Cache
				: 0; // 0000 1000

		// 只有指标 OR 都没有
		if (ops <= 0 || ops == Constants.Route_Func_Metrics) {
			return Chain.NONE;
		}

		// 必须有的功能
		ops |= Constants.Route_Func_Result;
		ops |= Constants.Route_Func_Param_Parse;
		ops |= Constants.Route_Func_Invoke;

		// 优先使用缓存
		if (Chain.chainCache.containsKey(ops)) {
			return Chain.chainCache.get(ops);
		}

		// 功能集合需要按照顺序的添加
		// Metrics -> *Result -> Permission -> MethodCache -> *ParamParse -> ParamCache
		// -> *Invoke
		int index = 0;
		Adapter[] adapters = new Adapter[8];
		if ((ops & Constants.Route_Func_Metrics) == Constants.Route_Func_Metrics) {
			adapters[index++] = new MetricsAdapter();
		}
		adapters[index++] = new ResultAdapter(resultHandler);
		if ((ops & Constants.Route_Func_Security) == Constants.Route_Func_Security) {
			adapters[index++] = new PermissionAdapter();
		}
		if ((ops & Constants.Route_Func_Method_Cache) == Constants.Route_Func_Method_Cache) {
			adapters[index++] = new MethodCacheAdapter(cacheStorage);
		}
		adapters[index++] = new ParamParseAdapter(paramParser);
		if ((ops & Constants.Route_Func_Param_Cache) == Constants.Route_Func_Param_Cache) {
			adapters[index++] = new ParamCacheAdapter(cacheStorage);
		}
		adapters[index++] = new InvokeAdapter();

		// 创建执行链
		return Chain.chain(ops, adapters);
	}

	/**
	 * 处理请求
	 */
	@Override
	@SuppressWarnings("unchecked")
	public void handle(Chain chain, RoutingContext context, MethodInvoker method) {

		if (logger.isDebugEnabled()) {
			logger.debug("Router {}", context.request().uri());
		}

		try {

			// 普通的处理方式
			if (chain == Chain.NONE) {
				Object metric = method.metrics != null ? method.metrics.begin() : null;
				Object[] params = this.paramParser.parseParameters(context, method);
				Object invokeResult = method.doInvoke(params);
				if (invokeResult != null && invokeResult instanceof CompletionStage) {
					CompletionStage<Object> resultFuture = (CompletionStage<Object>) invokeResult;
					resultFuture.whenComplete(
							(result, error) -> this.handleResultOnContext(result, error, context, method, metric));
				} else {
					this.handleResult(invokeResult, null, context, method, metric);
				}
				return;
			}

			// 责任链的处理方式
			chain.doChain(Context.of(context, method)).whenComplete(this.chainComplete);
		} catch (Throwable error) {
			this.resultHandler.handleError(error, context);
		}
	}

	/**
	 * 责任链的后续处理, 会判断是否需要输出
	 * 确保这里一定会有 Context
	 */
	protected void chainComplete(Context context, Throwable error) {
		if (context != null) {
			Throwable $error = error == null ? context.getError() : error;
			this.resultHandler.handleResult(null, $error, context.getContext());
		}
	}

	/**
	 * 在当前的Context中继续执行代码
	 */
	private void handleResultOnContext(Object result, Throwable error, RoutingContext context, MethodInvoker method,
			Object metric) {
		ContextInternal currContext = RoutingContexts.context(context);
		if (currContext != null) {
			currContext.runOnContext((vo) -> this.handleResult(result, error, context, method, metric));
		} else {
			this.handleResult(result, error, context, method, metric);
		}
	}

	/**
	 * 处理结果
	 */
	@SuppressWarnings("unchecked")
	private void handleResult(Object result, Throwable error, RoutingContext context, MethodInvoker method,
			Object metric) {
		try {
			resultHandler.handleResult(result, error, context);
		} finally {
			if (method.metrics != null) {
				String value = context.response().headers().get(HttpHeaders.CONTENT_LENGTH);
				long outputSize = StringUtils.isBlank(value) ? 0 : Long.parseLong(value);
				method.metrics.end(metric, outputSize, error == null);
			}
		}
	}
}